/**
 * jquery.meio.mask.js
 * @author: fabiomcosta
 * @version: 1.1.5
 *
 * Created by Fabio M. Costa on 2008-09-16. Please report any bug at http://www.meiocodigo.com
 *
 * Copyright (c) 2008 Fabio M. Costa http://www.meiocodigo.com
 *
 * The MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

(function ($) {

   var isIphone = (window.orientation != null);

   // browsers like firefox2 and before and opera doesnt have the onPaste event, but the paste feature can be done with the onInput event.
   var pasteEvent = (($.browser.opera || ($.browser.mozilla && parseFloat($.browser.version.substr(0, 3)) < 1.9)) ? 'input' : 'paste');

   // the timeout is set because we can't get the value from the input without it
   var pasteHandler = function (e) {
      e = $.event.fix(e || window.event);
      e.type = 'paste';
      var el = e.target;

      setTimeout(function () {
         $.event.dispatch.call(el, e);
      }, 1);
   };

   $.event.special.paste = {
      setup: function () {
         if (this.addEventListener)
            this.addEventListener(pasteEvent, pasteHandler, false);
         else if (this.attachEvent)
            this.attachEvent('on' + pasteEvent, pasteHandler);
      },

      teardown: function () {
         if (this.removeEventListener)
            this.removeEventListener(pasteEvent, pasteHandler, false);
         else if (this.detachEvent)
            this.detachEvent('on' + pasteEvent, pasteHandler);
      }
   };

   $.extend({
      mask: {

         // the mask rules. You may add yours!
         // number rules will be overwritten
         rules: {
            'z': /[a-z]/,
            'Z': /[A-Z]/,
            'a': /[a-zA-Z]/,
            '*': /[0-9a-zA-Z]/,
            '@': /[0-9a-zA-ZçÇáàãâéèêíìóòôõúùü]/
         },

         // these keys will be ignored by the mask.
         // all these numbers where obtained on the keydown event
         keyRepresentation: {
            8: 'backspace',
            9: 'tab',
            13: 'enter',
            16: 'shift',
            17: 'control',
            18: 'alt',
            27: 'esc',
            33: 'page up',
            34: 'page down',
            35: 'end',
            36: 'home',
            37: 'left',
            38: 'up',
            39: 'right',
            40: 'down',
            45: 'insert',
            46: 'delete',
            116: 'f5',
            123: 'f12',
            224: 'command'
         },

         iphoneKeyRepresentation: {
            10: 'go',
            127: 'delete'
         },

         signals: {
            '+': '',
            '-': '-'
         },

         // default settings for the plugin
         options: {
            attr: 'alt', // an attr to look for the mask name or the mask itself
            mask: null, // the mask to be used on the input
            type: 'fixed', // the mask of this mask
            maxLength: -1, // the maxLength of the mask
            defaultValue: '', // the default value for this input
            signal: false, // this should not be set, to use signal at masks put the signal you want ('-' or '+') at the default value of this mask.
            // See the defined masks for a better understanding.

            textAlign: true, // use false to not use text-align on any mask (at least not by the plugin, you may apply it using css)
            selectCharsOnFocus: true, // select all chars from input on its focus
            autoTab: true, // auto focus the next form element when you type the mask completely
            setSize: false, // sets the input size based on the length of the mask (work with fixed and reverse masks only)
            fixedChars: '[(),.:/ -]', // fixed chars to be used on the masks. You may change it for your needs!

            onInvalid: function () { },
            onValid: function () { },
            onOverflow: function () { }
         },

         // masks. You may add yours!
         // Ex: $.fn.setMask.masks.msk = {mask: '999'}
         // and then if the 'attr' options value is 'alt', your input should look like:
         // <input type="text" name="some_name" id="some_name" alt="msk" />
         masks: {
            'phone': { mask: '(99) 9999-9999' },
            'phone-us': { mask: '(999) 999-9999' },
            'cpf': { mask: '999.999.999-99' }, // cadastro nacional de pessoa fisica (kind of a brazillian ssn)
            'cnpj': { mask: '99.999.999/9999-99' },
            'date': { mask: '39/19/9999' },     // uk date
            'date-us': { mask: '19/39/9999' },
            'cep': { mask: '99999-999' },
            'time': { mask: '29:59' },
            'cc': { mask: '9999 9999 9999 9999' }, // credit card
            'integer': { mask: '999.999.999.999', type: 'reverse' },
            'decimal': { mask: '99,999.999.999.999', type: 'reverse', defaultValue: '000' },
            'decimal-us': { mask: '99.999,999,999,999', type: 'reverse', defaultValue: '000' },
            'signed-decimal': { mask: '99,999.999.999.999', type: 'reverse', defaultValue: '+000' },
            'signed-decimal-us': { mask: '99,999.999.999.999', type: 'reverse', defaultValue: '+000' }
         },

         init: function () {
            // if has not inited...
            if (!this.hasInit) {

               var self = this, i,
                        keyRep = (isIphone) ? this.iphoneKeyRepresentation : this.keyRepresentation;

               this.ignore = false;

               // constructs number rules
               for (i = 0; i <= 9; i++) this.rules[i] = new RegExp('[0-' + i + ']');

               this.keyRep = keyRep;
               // ignore keys array creation for iphone or the normal ones
               this.ignoreKeys = [];
               $.each(keyRep, function (key) {
                  self.ignoreKeys.push(parseInt(key, 10));
               });

               this.hasInit = true;
            }
         },

         set: function (el, options) {

            var maskObj = this,
                    $el = $(el),
                    mlStr = 'maxLength';

            options = options || {};
            this.init();

            return $el.each(function () {

               if (options.attr) maskObj.options.attr = options.attr;

               var $this = $(this),
                        o = $.extend({}, maskObj.options),
                        attrValue = $this.attr(o.attr),
                        tmpMask = '';

               // then we look for the 'attr' option
               tmpMask = (typeof options == 'string') ? options : (attrValue !== '') ? attrValue : null;
               if (tmpMask) o.mask = tmpMask;

               // then we see if it's a defined mask
               if (maskObj.masks[tmpMask]) o = $.extend(o, maskObj.masks[tmpMask]);

               // then it looks if the options is an object, if it is we will overwrite the actual options
               if (typeof options == 'object' && options.constructor != Array) o = $.extend(o, options);

               //then we look for some metadata on the input
               if ($.metadata) o = $.extend(o, $this.metadata());

               if (o.mask != null) {

                  // prevents javascript automatic type convertion
                  o.mask += '';

                  if ($this.data('mask')) maskObj.unset($this);

                  var defaultValue = o.defaultValue,
                            reverse = (o.type === 'reverse'),
                            fixedCharsRegG = new RegExp(o.fixedChars, 'g');

                  if (o.maxLength === -1) o.maxLength = $this.attr(mlStr);

                  o = $.extend({}, o, {
                     fixedCharsReg: new RegExp(o.fixedChars),
                     fixedCharsRegG: fixedCharsRegG,
                     maskArray: o.mask.split(''),
                     maskNonFixedCharsArray: o.mask.replace(fixedCharsRegG, '').split('')
                  });

                  // setSize option (this is kept when the mask is removed)
                  if ((o.type == 'fixed' || reverse) && o.setSize && !$this.attr('size')) $this.attr('size', o.mask.length);

                  // sets text-align right for reverse masks
                  if (reverse && o.textAlign) $this.css('text-align', 'right');

                  if (this.value !== '' || defaultValue !== '') {
                     // apply mask to the current value of the input or to the default value
                     var val = maskObj.string((this.value !== '') ? this.value : defaultValue, o);
                     //setting defaultValue fixes the reset button from the form
                     this.defaultValue = val;
                     $this.val(val);
                  }

                  // compatibility patch for infinite mask, that is now repeat
                  if (o.type == 'infinite') o.type = 'repeat';

                  $this.data('mask', o);

                  // removes the maxLength attribute (it will be set again if you use the unset method)
                  $this.removeAttr(mlStr);

                  // setting the input events
                  $this.bind('keydown.mask', { func: maskObj._onKeyDown, thisObj: maskObj }, maskObj._onMask)
                            .bind('keypress.mask', { func: maskObj._onKeyPress, thisObj: maskObj }, maskObj._onMask)
                            .bind('keyup.mask', { func: maskObj._onKeyUp, thisObj: maskObj }, maskObj._onMask)
                            .bind('paste.mask', { func: maskObj._onPaste, thisObj: maskObj }, maskObj._onMask)
                            .bind('focus.mask', maskObj._onFocus)
                            .bind('blur.mask', maskObj._onBlur)
                            .bind('change.mask', maskObj._onChange);
               }
            });
         },

         //unsets the mask from el
         unset: function (el) {
            var $el = $(el);

            return $el.each(function () {
               var $this = $(this);
               if ($this.data('mask')) {
                  var maxLength = $this.data('mask').maxLength;
                  if (maxLength != -1) $this.attr('maxLength', maxLength);
                  $this.unbind('.mask')
                            .removeData('mask');
               }
            });
         },

         //masks a string
         string: function (str, options) {
            this.init();
            var o = {};
            if (typeof str != 'string') str = String(str);
            switch (typeof options) {
               case 'string':
                  // then we see if it's a defined mask
                  if (this.masks[options]) o = $.extend(o, this.masks[options]);
                  else o.mask = options;
                  break;
               case 'object':
                  o = options;
            }
            if (!o.fixedChars) o.fixedChars = this.options.fixedChars;

            var fixedCharsReg = new RegExp(o.fixedChars),
                    fixedCharsRegG = new RegExp(o.fixedChars, 'g');

            // insert signal if any
            if ((o.type === 'reverse') && o.defaultValue) {
               if (typeof this.signals[o.defaultValue.charAt(0)] != 'undefined') {
                  var maybeASignal = str.charAt(0);
                  o.signal = (typeof this.signals[maybeASignal] != 'undefined') ? this.signals[maybeASignal] : this.signals[o.defaultValue.charAt(0)];
                  o.defaultValue = o.defaultValue.substring(1);
               }
            }

            return this.__maskArray(str.split(''),
                            o.mask.replace(fixedCharsRegG, '').split(''),
                            o.mask.split(''),
                            o.type,
                            o.maxLength,
                            o.defaultValue,
                            fixedCharsReg,
                            o.signal);
         },

         // all the 3 events below are here just to fix the change event on reversed masks.
         // It isn't fired in cases that the keypress event returns false (needed).
         _onFocus: function (e) {
            var $this = $(this), dataObj = $this.data('mask');
            dataObj.inputFocusValue = $this.val();
            dataObj.changed = false;
            if (dataObj.selectCharsOnFocus) $this.select();
         },

         _onBlur: function (e) {
            var $this = $(this), dataObj = $this.data('mask');
            if (dataObj.inputFocusValue != $this.val() && !dataObj.changed)
               $this.trigger('change');
         },

         _onChange: function (e) {
            $(this).data('mask').changed = true;
         },

         _onMask: function (e) {
            var thisObj = e.data.thisObj,
                    o = {};

            o._this = e.target;
            o.$this = $(o._this);
            o.data = o.$this.data('mask');

            if (o.$this.attr('readonly') || !o.data) {
               return true;
            }

            o[o.data.type] = true;
            o.value = o.$this.val();
            o.nKey = thisObj.__getKeyNumber(e);
            o.range = thisObj.__getRange(o._this);
            o.valueArray = o.value.split('');
            return e.data.func.call(thisObj, e, o);
         },

         _onKeyDown: function (e, o) {
            // lets say keypress at desktop == keydown at iphone (theres no keypress at iphone)
            this.ignore = $.inArray(o.nKey, this.ignoreKeys) > -1 || e.ctrlKey || e.metaKey || e.altKey;
            if (this.ignore) {
               var rep = this.keyRep[o.nKey];
               o.data.onValid.call(o._this, rep || '', o.nKey);
            }
            return isIphone ? this._onKeyPress(e, o) : true;
         },

         _onKeyUp: function (e, o) {
            //9=TAB_KEY 16=SHIFT_KEY
            //this is a little bug, when you go to an input with tab key
            //it would remove the range selected by default, and that's not a desired behavior
            if (o.nKey === 9 || o.nKey === 16) return true;

            if (o.repeat) {
               this.__autoTab(o);
               return true;
            }

            return this._onPaste(e, o);
         },

         _onPaste: function (e, o) {
            // changes the signal at the data obj from the input
            if (o.reverse) this.__changeSignal(e.type, o);

            var $thisVal = this.__maskArray(
                    o.valueArray,
                    o.data.maskNonFixedCharsArray,
                    o.data.maskArray,
                    o.data.type,
                    o.data.maxLength,
                    o.data.defaultValue,
                    o.data.fixedCharsReg,
                    o.data.signal
                );

            o.$this.val($thisVal);
            // this makes the caret stay at first position when
            // the user removes all values in an input and the plugin adds the default value to it (if it haves one).
            if (!o.reverse && o.data.defaultValue.length && (o.range.start === o.range.end))
               this.__setRange(o._this, o.range.start, o.range.end);

            //fix so ie's and safari's caret won't go to the end of the input value.
            if (($.browser.msie || $.browser.safari) && !o.reverse)
               this.__setRange(o._this, o.range.start, o.range.end);

            if (this.ignore) return true;

            this.__autoTab(o);
            return true;
         },

         _onKeyPress: function (e, o) {

            if (this.ignore) return true;

            // changes the signal at the data obj from the input
            if (o.reverse) this.__changeSignal(e.type, o);

            var c = String.fromCharCode(o.nKey),
                    rangeStart = o.range.start,
                    rawValue = o.value,
                    maskArray = o.data.maskArray;

            if (o.reverse) {
               // the input value from the range start to the value start
               var valueStart = rawValue.substr(0, rangeStart),
               // the input value from the range end to the value end
                        valueEnd = rawValue.substr(o.range.end, rawValue.length);
               console.log(o.reverse);
               rawValue = valueStart + c + valueEnd;
               //necessary, if not decremented you will be able to input just the mask.length-1 if signal!=''
               //ex: mask:99,999.999.999 you will be able to input 99,999.999.99
               if (o.data.signal && (rangeStart - o.data.signal.length > 0)) {
                  rangeStart -= o.data.signal.length;
               }
            }

            var valueArray = rawValue.replace(o.data.fixedCharsRegG, '').split(''),
            // searches for fixed chars begining from the range start position, till it finds a non fixed
                    extraPos = this.__extraPositionsTill(rangeStart, maskArray, o.data.fixedCharsReg);

            o.rsEp = rangeStart + extraPos;

            if (o.repeat) {
               o.rsEp = 0;
            }

            // if the rule for this character doesnt exist (value.length is bigger than mask.length)
            // added a verification for maxLength in the case of the repeat type mask
            if (!this.rules[maskArray[o.rsEp]] || (o.data.maxLength != -1 && valueArray.length >= o.data.maxLength && o.repeat)) {
               // auto focus on the next input of the current form
               o.data.onOverflow.call(o._this, c, o.nKey);
               return false;
            }

            // if the new character is not obeying the law...
            else if (!this.rules[maskArray[o.rsEp]].test(c)) {
               o.data.onInvalid.call(o._this, c, o.nKey);
               return false;
            }

            else {
               o.data.onValid.call(o._this, c, o.nKey);
            }

            var $thisVal = this.__maskArray(
                    valueArray,
                    o.data.maskNonFixedCharsArray,
                    maskArray,
                    o.data.type,
                    o.data.maxLength,
                    o.data.defaultValue,
                    o.data.fixedCharsReg,
                    o.data.signal,
                    extraPos
                );

            if (!o.repeat) {
               o.$this.val($thisVal);
            }

            return (o.reverse) ? this._keyPressReverse(e, o) : (o.fixed) ? this._keyPressFixed(e, o) : true;
         },

         _keyPressFixed: function (e, o) {

            if (o.range.start == o.range.end) {
               // the 0 thing is because theres an unwanted behavior when you put a default
               // value on a fixed mask and you select the value from the input the range would go to the
               // end of the string when you enter a char. with this it will overwrite the first char wich is a better behavior.
               // opera fix, cant have range value bigger than value length, i think it loops thought the input value...
               if ((o.rsEp === 0 && o.value.length === 0) || o.rsEp < o.value.length)
                  this.__setRange(o._this, o.rsEp, o.rsEp + 1);
            }
            else
               this.__setRange(o._this, o.range.start, o.range.end);

            return true;
         },

         _keyPressReverse: function (e, o) {
            // fix for ie
            // this bug was pointed by Pedro Martins
            // it fixes a strange behavior that ie was having after a char was inputted in a text input that
            // had its content selected by any range
            if ($.browser.msie && ((o.range.start === 0 && o.range.end === 0) || o.range.start != o.range.end))
               this.__setRange(o._this, o.value.length);
            return false;
         },

         __autoTab: function (o) {
            if (o.data.autoTab && (
                        (
                            o.$this.val().length >= o.data.maskArray.length &&
                            !o.repeat
                        ) || (
                            o.data.maxLength != -1 &&
                            o.valueArray.length >= o.data.maxLength &&
                            o.repeat
                        )
                    )
                ) {
               var nextEl = this.__getNextInput(o._this, o.data.autoTab);
               if (nextEl) {
                  o.$this.trigger('blur');
                  nextEl.focus().select();
               }
            }
         },

         // changes the signal at the data obj from the input
         __changeSignal: function (eventType, o) {
            if (o.data.signal !== false) {
               var inputChar = (eventType === 'paste') ? o.value.charAt(0) : String.fromCharCode(o.nKey);
               if (this.signals && (typeof this.signals[inputChar] !== 'undefined')) {
                  o.data.signal = this.signals[inputChar];
               }
            }
         },

         __getKeyNumber: function (e) {
            return (e.charCode || e.keyCode || e.which);
         },

         // this function is totaly specific to be used with this plugin, youll never need it
         // it gets the array representing an unmasked string and masks it depending on the type of the mask
         __maskArray: function (valueArray, maskNonFixedCharsArray, maskArray, type, maxlength, defaultValue, fixedCharsReg, signal, extraPos) {
            if (type === 'reverse') valueArray.reverse();
            valueArray = this.__removeInvalidChars(valueArray, maskNonFixedCharsArray, type === 'repeat' || type === 'infinite');
            if (defaultValue) valueArray = this.__applyDefaultValue.call(valueArray, defaultValue);
            valueArray = this.__applyMask(valueArray, maskArray, extraPos, fixedCharsReg);
            switch (type) {
               case 'reverse':
                  valueArray.reverse();
                  return (signal || '') + valueArray.join('').substring(valueArray.length - maskArray.length);
               case 'infinite': case 'repeat':
                  var joinedValue = valueArray.join('');
                  return (maxlength !== -1 && valueArray.length >= maxlength) ? joinedValue.substring(0, maxlength) : joinedValue;
               default:
                  return valueArray.join('').substring(0, maskArray.length);
            }
            return '';
         },

         // applyes the default value to the result string
         __applyDefaultValue: function (defaultValue) {
            var defLen = defaultValue.length,
                    thisLen = this.length,
                    i;
            //removes the leading chars
            for (i = thisLen - 1; i >= 0; i--) {
               if (this[i] == defaultValue.charAt(0)) {
                  this.pop();
               }
               else break;
            }
            // apply the default value
            for (i = 0; i < defLen; i++) if (!this[i]) {
               this[i] = defaultValue.charAt(i);
            }

            return this;
         },

         // Removes values that doesnt match the mask from the valueArray
         // Returns the array without the invalid chars.
         __removeInvalidChars: function (valueArray, maskNonFixedCharsArray, repeatType) {
            // removes invalid chars
            for (var i = 0, y = 0; i < valueArray.length; i++) {
               if (maskNonFixedCharsArray[y] &&
                        this.rules[maskNonFixedCharsArray[y]] &&
                        !this.rules[maskNonFixedCharsArray[y]].test(valueArray[i])) {
                  valueArray.splice(i, 1);
                  if (!repeatType) y--;
                  i--;
               }
               if (!repeatType) y++;
            }
            return valueArray;
         },

         // Apply the current input mask to the valueArray and returns it.
         __applyMask: function (valueArray, maskArray, plus, fixedCharsReg) {
            if (typeof plus == 'undefined') plus = 0;
            // apply the current mask to the array of chars
            for (var i = 0; i < valueArray.length + plus; i++) {
               if (maskArray[i] && fixedCharsReg.test(maskArray[i]))
                  valueArray.splice(i, 0, maskArray[i]);
            }
            return valueArray;
         },

         // searches for fixed chars begining from the range start position, till it finds a non fixed
         __extraPositionsTill: function (rangeStart, maskArray, fixedCharsReg) {
            var extraPos = 0;
            while (fixedCharsReg.test(maskArray[rangeStart++])) {
               extraPos++;
            }
            return extraPos;
         },

         __getNextInput: function (input, selector) {
            var form = input.form;

            if (form == null) {
               return null;
            }

            var formEls = form.elements,
                    initialInputIndex = $.inArray(input, formEls) + 1,
                    len = formEls.length,
                    $input = null,
                    i;

            // look for next input on the form of the pased input
            for (i = initialInputIndex; i < len; i++) {
               $input = $(formEls[i]);
               if (this.__isNextInput($input, selector)) {
                  return $input;
               }
            }

            var forms = document.forms,
                    initialFormIndex = $.inArray(input.form, forms) + 1,
                    y, tmpFormEls, _len = forms.length;
            // look for the next forms for the next input
            for (y = initialFormIndex; y < _len; y++) {
               tmpFormEls = forms[y].elements;
               len = tmpFormEls.length;
               for (i = 0; i < len; i++) {
                  $input = $(tmpFormEls[i]);
                  if (this.__isNextInput($input, selector)) {
                     return $input;
                  }
               }
            }
            return null;
         },

         __isNextInput: function ($formEl, selector) {
            var formEl = $formEl.get(0);
            return formEl &&
                    (formEl.offsetWidth > 0 || formEl.offsetHeight > 0) &&
                    formEl.nodeName != 'FIELDSET' &&
                    (selector === true || (typeof selector == 'string' && $formEl.is(selector)));
         },

         // http://www.bazon.net/mishoo/articles.epl?art_id=1292
         __setRange: function (input, start, end) {
            if (typeof end == 'undefined') {
               end = start;
            }
            if (input.setSelectionRange) {
               input.setSelectionRange(start, end);
            } else {
               // assumed IE
               var range = input.createTextRange();
               range.collapse();
               range.moveStart('character', start);
               range.moveEnd('character', end - start);
               range.select();
            }
         },

         // adaptation from http://digitarald.de/project/autocompleter/
         __getRange: function (input) {
            if (!$.browser.msie) return { start: input.selectionStart, end: input.selectionEnd };
            var pos = { start: 0, end: 0 },
                    range = document.selection.createRange();
            pos.start = 0 - range.duplicate().moveStart('character', -100000);
            pos.end = pos.start + range.text.length;
            return pos;
         },

         //deprecated
         unmaskedVal: function (el) {
            return $(el).val().replace($.mask.fixedCharsRegG, '');
         }

      }
   });

   $.fn.extend({
      setMask: function (options) {
         return $.mask.set(this, options);
      },
      unsetMask: function () {
         return $.mask.unset(this);
      },
      //deprecated
      unmaskedVal: function () {
         return $.mask.unmaskedVal(this[0]);
      },
      keypad: function (e,o) {

         
      }
   });
})(jQuery);
